﻿using System;
using System.Collections.Generic;
using System.Text;
using Sio.Mdm.Utils.Collections;
using System.Threading;
using System.Xml;

namespace Sio.Mdm.Utils
{
    #region Scheduler
    /// <summary>
    /// Implementation of job scheduler.
    /// </summary>
    /// <remarks>
    /// Tasks are executed with 1 second resolution.
    /// Setting task resolution to lower values has no effect.
    /// </remarks>
    public class Scheduler : IDisposable
    {
        #region Fields
        private const int Resolution = 1000;
        private Thread _threadHandle;
        private ManualResetEvent _exitEvent;
        private ManualResetEvent _waitEvent;
        private object JobsLockObject = new object();
        private int JobID = 1;
        #endregion

        #region Construction
        /// <summary>
        /// Construct scheduler
        /// </summary>
        public Scheduler()
        {   
        }
        #endregion

        #region Finalizer
        /// <summary>
        /// Finalizer for scheduler
        /// </summary>
        ~Scheduler()
        {
            Dispose(false);
        }
        #endregion

        #region Properties

        #region Public

        #region Jobs
        private SchedulerJobsCollection _jobs;
        /// <summary>
        /// Gets list of jobs assigned to schedule
        /// </summary>
        public SchedulerJobsCollection Jobs
        {
            get
            {
                lock (JobsLockObject)
                {
                    if (_isDisposed)
                    {
                        throw new ObjectDisposedException(ToString());
                    }

                    if (null == _jobs)
                    {
                        _jobs = new SchedulerJobsCollection(this);
                    }

                    return _jobs;
                }
            }

        }
        #endregion

        #region IsDisposed
        private bool _isDisposed;
        /// <summary>
        /// Gets wheater instance was disposed
        /// </summary>
        public bool IsDisposed
        {
            get
            {
                return _isDisposed;
            }
        }
        #endregion

        #region IsRunning
        /// <summary>
        /// Gets wheater scheduler is currently scheduling jobs
        /// </summary>
        /// <seealso cref="Start"/>
        /// <seealso cref="Stop"/>
        public bool IsRunning
        {
            get
            {
                if (_isDisposed)
                {
                    throw new ObjectDisposedException(ToString());
                }

                return _threadHandle != null;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region ParseConfig
        /// <summary>
        /// Return instance of scheduler parsed from XML config
        /// </summary>
        /// <returns>Instance of scheduler from XML config or null if error occurs</returns>
        public static Scheduler FromXmlConfig(string xmlConfig)
        {
            try
            {
                Scheduler schedulerInstance = new Scheduler();
                XmlDocument doc = new XmlDocument();

                doc.LoadXml(xmlConfig);

                XmlNodeList nodes = doc.SelectNodes("scheduler/schedules/schedule");

                foreach (XmlNode node in nodes)
                {
                    ParseSchedule(node, schedulerInstance);
                }

                return schedulerInstance;
            }
            catch(Exception ex)
            {
                Logger.WriteError("Failed to parse scheduler config");
                Logger.WriteException(ex);
            }

            return null;
        }
        #endregion

        #region ToXml
        /// <summary>
        /// Return current config as XML string
        /// </summary>
        /// <returns>XML of scheduler configutration</returns>
        public static string ToXml()
        {
            throw new NotImplementedException();
        }
        #endregion

        #region Start, Stop
        /// <summary>
        /// Start job scheduling.
        /// </summary>
        public void Start()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(ToString());
            }

            if (IsRunning)
            {
                return;
            }

            _waitEvent = new ManualResetEvent(false);
            _exitEvent = new ManualResetEvent(false);
            _threadHandle = new Thread(new ThreadStart(SchedulerLoop));
            _threadHandle.IsBackground = true;
            _threadHandle.Start();
        }
        /// <summary>
        /// Stop job scheduling
        /// </summary>
        public void Stop()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(ToString());
            }

            if (!IsRunning)
            {
                return;
            }

            // Signal thread there is time to stop
            _waitEvent.Set();

            // Wait for thread to stop executing
            _exitEvent.WaitOne();

            _threadHandle = null;
            _waitEvent = _exitEvent = null;
        }
        #endregion

        #region IDisposable Members
        /// <summary>
        /// Dispose scheduler instance
        /// </summary>
        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            Dispose(true);
            _isDisposed = true;
        }
        #endregion

        #region Schedule
        /// <summary>
        /// Schedule new job based on <see cref="Schedule"/> type at specified time
        /// and with specified argument
        /// </summary>
        /// <param name="type">One of the <see cref="Schedule"/> value</param>
        /// <param name="time">Time dependant on schedule type</param>
        /// <param name="argument">User defined argument for job</param>
        /// <returns>Id of newly created job or -1 if error occured</returns>
        public int ScheduleJob(Schedule type, DateTime time, string argument)
        {
            SchedulerJobBase job = null;

            switch (type)
            {
                case Schedule.Once:
                    job = new SchedulerJobBase(time);
                    break;

                case Schedule.ByDay:
                    job = new DailyJob(time);
                    break;

                case Schedule.ByHour:
                    job = new HourlyJob(time);
                    break;

                case Schedule.ByMinute:
                    time = DateTime.Now.AddSeconds(DateTime.Now.Second);
                    job = new SchedulerJobBase(time, TimeSpan.FromMinutes(1));
                    break;

                case Schedule.ByMonth:
                    job = new MonthlyJob(DayOfMonth.Defined, time);
                    break;

                case Schedule.BySecond:
                    time = DateTime.Now;
                    job = new SchedulerJobBase(time, TimeSpan.FromSeconds(1));
                    break;

                case Schedule.ByWeek:
                    job = new WeeklyJob(time.DayOfWeek, time);
                    break;

                case Schedule.ByYear:
                    job = new YearlyJob(time);
                    break;
            }

            if (job != null)
            {
                job.Argument = argument;

                lock (JobsLockObject)
                {
                    job.ID = JobID++;
                    Jobs.Add(job);

                    return job.ID;
                }
            }

            return -1;
        }
        /// <summary>
        /// Scheduly weekly reccuring job at spcified <see cref="DayOfWeek"/>
        /// and time with specified argument
        /// </summary>
        /// <param name="day">One of <see cref="DayOfWeek"/> values</param>
        /// <param name="time">Time of day job should be scheduled</param>
        /// <param name="argument">Argument for job</param>
        /// <returns>Id of created job</returns>
        public int ScheduleJob(DayOfWeek day, DateTime time, string argument)
        {
            WeeklyJob job = new WeeklyJob(day, time);
            
            job.Argument = argument;
            job.ID = JobID++;
            Jobs.Add(job);

            return job.ID;
        }
        /// <summary>
        /// Schedule reccuring job starting at specific time and executed at specified
        /// time span.
        /// </summary>
        /// <param name="startTime">Start time of reccurence</param>
        /// <param name="reccurence">Time span between executions</param>
        /// <param name="argument">Argument for job</param>
        /// <returns>ID of scheduled job</returns>
        public int ScheduleJob(DateTime startTime, TimeSpan reccurence, string argument)
        {
            SchedulerJobBase job = new SchedulerJobBase(startTime, reccurence);
            job.ScheduleType = ScheduleRecurrence.Reccuring;

            job.Argument = argument;
            job.ID = JobID++;
            Jobs.Add(job);

            return job.ID;
        }
        #endregion

        #region RemoveJob
        /// <summary>
        /// Remove job from scheduling
        /// </summary>
        /// <param name="jobId"></param>
        public void RemoveJob(int jobId)
        {
            SchedulerJobBase job = GetJobById(jobId);

            if (job != null)
            {
                Jobs.Remove(job);
            }
        }
        #endregion

        #region Reschedule
        /// <summary>
        /// Recalculate job schedule time. Skip next scheduling time
        /// </summary>
        /// <param name="jobId"></param>
        public void RescheduleJob(int jobId)
        {
            SchedulerJobBase job = GetJobById(jobId);

            if (job != null)
            {
                job.CalculateReccurence();
            }
        }
        #endregion

        #region GetJobById
        /// <summary>
        /// Return instance of job by ID
        /// </summary>
        /// <param name="id">IS of job to retreive</param>
        /// <returns>Job instance or null if no job with specified ID exist</returns>
        /// <seealso cref="SchedulerJobBase"/>
        public SchedulerJobBase GetJobById(int id)
        {
            return Jobs.Find(new Predicate<SchedulerJobBase>
            (
                delegate(SchedulerJobBase job)
                {
                    return job.ID == id;
                }
            ));
        }
        #endregion

        #endregion

        #region Private

        #region Dispose
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                Jobs.Dispose();
                _jobs = null;
            }

            if (IsDisposed)
            {
                return;
            }
                        
            if (IsRunning)
            {
                Stop();
            }
        }
        #endregion

        #region SchedulerLoop
        private void SchedulerLoop()
        {
            bool exit = false;            

            while (!exit)
            {
                DateTime executionTime = DateTime.Now;

                lock (JobsLockObject)
                {
                    SchedulerJobBase[] jobs = Jobs.ToArray();

                    foreach (SchedulerJobBase job in jobs)
                    {
                        // Check wheater we should exit job's loop. 
                        // Just for performance reason.
                        if (_waitEvent.WaitOne(0, false))
                        {
                            break;
                        }

                        // Should job be exeucted
                        if (job.ExecutionTime > executionTime || job.IsBusy)
                        {
                            continue;
                        }

                        // First calculate next occurence so in case
                        // execution is late. we'll scheduled 
                        // job again next time. At least we can be sure job
                        // will execute at some time.
                        if (job.ScheduleType == ScheduleRecurrence.Reccuring)
                        {
                            job.CalculateReccurence();
                        }
                        else
                        {
                            Jobs.Remove(job);
                        }
                        
                        // run job execution
                        job.Execute();
                    }   
                    
                }

                exit = _waitEvent.WaitOne(Resolution, false);                
            }

            // Signal that thread has become an ex-thread 
            // (this thread is no more, perished, pushing up the daisies,  ... :)
            _exitEvent.Set();
        }
        #endregion
        
        #region InvokeExecuteJob
        private void InvokeExecuteJob(SchedulerJobBase job)
        {
            if (ExecuteJob != null)
            {
                ExecuteJob(this, new ExecuteJobEventArgs(job));
            }
        }
        #endregion

        #region ParseSchedule
        private static void ParseSchedule(XmlNode scheduleNode, Scheduler scheduler)
        {
            Schedule schedule = Schedule.Once;
            DateTime time;
            string temp = Sio.Mdm.Utils.ConfigHelper.GetAttributeValueByName(scheduleNode, "type");

            if (!String.IsNullOrEmpty(temp))
            {
                try
                {
                    schedule = (Schedule)Enum.Parse(typeof(Schedule), temp, true);
                }
                catch
                {
                    // Can't determin schedule type
                    return;
                }
            }


            temp = Sio.Mdm.Utils.ConfigHelper.GetAttributeValueByName(scheduleNode, "time");

            // Special case to determine day of week
            if (schedule == Schedule.ByWeek)
            {
                DayOfWeek day = DateTime.Now.DayOfWeek;
                time = DateTime.Today;

                string[] parts = temp.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                foreach (string part in parts)
                {
                    try { day = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), part); }
                    catch { }

                    try { time = DateTime.Parse(part); }
                    catch { }
                }

                scheduler.ScheduleJob(day, time, Sio.Mdm.Utils.ConfigHelper.GetAttributeValueByName(scheduleNode, "argument"));
            }
            else
            {
                if (!DateTime.TryParse(temp, out time))
                {
                    // Can't determine schedule time
                    return;
                }

                scheduler.ScheduleJob(schedule, time, Sio.Mdm.Utils.ConfigHelper.GetAttributeValueByName(scheduleNode, "argument"));
            }


        }
        #endregion

        #endregion

        #endregion

        #region Events

        #region Public
        /// <summary>
        /// Fired when job is scheduled for execution
        /// </summary>
        public event EventHandler<ExecuteJobEventArgs> ExecuteJob;
        #endregion

        #endregion

        #region Classes

        #region SchedulerJobsCollection
        /// <summary>
        /// List of scheduler jobs
        /// </summary>
        public class SchedulerJobsCollection : BaseList<Scheduler.SchedulerJobBase>
        {
            #region Fields
            private Scheduler Owner;
            #endregion

            #region Construction
            /// <summary>
            /// COnstruct list of jobs
            /// </summary>
            /// <param name="owner"></param>
            public SchedulerJobsCollection(Scheduler owner)
            {
                if (null == owner)
                {
                    throw new ArgumentNullException("owner");
                }

                Owner = owner;
            }
            #endregion

            #region Methods

            #region Protected

            #region OnAfterInsert, OnAfterRemove
            /// <summary>
            /// Overriden to assign scheduler instance
            /// </summary>
            /// <param name="index"></param>
            /// <param name="value"></param>
            protected override void OnAfterInsert(int index, Scheduler.SchedulerJobBase value)
            {
                value.Scheduler = Owner;
                base.OnAfterInsert(index, value);
            }
            /// <summary>
            /// Overriden to remove scheduler instance
            /// </summary>
            /// <param name="value"></param>
            protected override void OnAfterRemove(Scheduler.SchedulerJobBase value)
            {
                value.Scheduler = null;
                base.OnAfterRemove(value);
            }
            #endregion

            #region OnBeforeClear
            /// <summary>
            /// Overriden to remove scheduler instance
            /// </summary>
            /// <returns></returns>
            protected override bool OnBeforeClear()
            {
                foreach (Scheduler.SchedulerJobBase job in this)
                {
                    job.Terminate();
                }

                return base.OnBeforeClear();
            }
            #endregion

            #endregion

            #endregion
        }
        #endregion

        #region SchedulerJobBase
        /// <summary>
        /// Implementation of base scheduler job
        /// </summary>
        public class SchedulerJobBase
        {
            #region Fields
            private Thread JobThread;            
            #endregion

            #region Construction
            /// <summary>
            /// Construct scheduler job
            /// </summary>
            public SchedulerJobBase()
            {
            }
            /// <summary>
            /// Construct scheduled job with specified execution time
            /// </summary>
            /// <param name="executionTime"></param>
            public SchedulerJobBase(DateTime executionTime)
                : this(ScheduleRecurrence.Once)
            {
                ExecutionTime = executionTime;
            }
            /// <summary>
            /// Construct scheduled job with specified starting execution time and specified
            /// time span between executions
            /// </summary>
            /// <param name="firstExecutionTime"></param>
            /// <param name="reccuringInterval"></param>
            public SchedulerJobBase(DateTime firstExecutionTime, TimeSpan reccuringInterval)
                : this(ScheduleRecurrence.Reccuring)
            {
                ExecutionTime = firstExecutionTime;
                Interval = reccuringInterval;
            }
            /// <summary>
            /// Schedule jbo with specified reccurence type
            /// </summary>
            /// <param name="type">Type of reccurence</param>
            /// <seealso cref="ScheduleRecurrence"/>
            protected SchedulerJobBase(ScheduleRecurrence type)
            {
                ScheduleType = type;
            }
            #endregion

            #region Properties

            #region Public

            #region ExecutionTime
            private DateTime _executionTime = DateTime.Now;
            /// <summary>
            /// Gets or sets next execution time. This is automaticly adjusted
            /// when <see cref="ScheduleType"/> is set to 
            /// <see cref="ScheduleRecurrence.Reccuring"/> and job has been executed.
            /// </summary>
            public DateTime ExecutionTime
            {
                get
                {
                    return _executionTime;
                }
                set
                {
                    _executionTime = value;
                }
            }
            #endregion

            #region ScheduleType
            private ScheduleRecurrence _type;
            /// <summary>
            /// Type of reccurence for job
            /// </summary>
            public ScheduleRecurrence ScheduleType
            {
                get
                {
                    return _type;
                }
                set
                {
                    _type = value;
                }
            }
            #endregion

            #region Interval
            private TimeSpan _interval;
            /// <summary>
            /// Gets or sets interval between two executions
            /// </summary>
            public TimeSpan Interval
            {
                get
                {
                    return _interval;
                }
                set
                {
                    _interval = value;
                }
            }
            #endregion

            #region Argument
            private string _argument = null;
            /// <summary>
            /// Gets or sets user defined argument for job execution
            /// </summary>
            public string Argument
            {
                get
                {
                    return _argument;
                }
                set
                {
                    _argument = value;
                }
            }
            #endregion

            #region ID
            private int _id;
            /// <summary>
            /// ID of job assigned by scheduler
            /// </summary>
            public int ID
            {
                get
                {
                    return _id;
                }
                internal set
                {
                    _id = value;
                }
            }
            #endregion

            #region IsBusy
            /// <summary>
            /// Check wheater job is currently running
            /// </summary>
            public bool IsBusy
            {
                get
                {
                    return JobThread != null;
                }
            }
            #endregion

            #endregion

            #region Protected

            #region Scheduler
            private Scheduler _scheduler;
            /// <summary>
            /// Gets instance of <see cref="Scheduler"/> job belongs
            /// </summary>
            internal protected Scheduler Scheduler
            {
                get
                {
                    return _scheduler;
                }
                internal set
                {
                    _scheduler = value;
                }
            }
            #endregion

            #endregion

            #endregion

            #region Methods

            #region Public

            #region Run, Stop
            /// <summary>
            /// Run job immediately
            /// </summary>
            public void Run()
            {
                Execute();
            }
            /// <summary>
            /// Stop job execution
            /// </summary>
            public void Stop()
            {
                Terminate();
            }
            #endregion

            #region Reschedule
            /// <summary>
            /// Skip current schedule time
            /// </summary>
            public void Reschedule()
            {
                CalculateReccurence();
            }
            #endregion

            #endregion

            #region Internal

            #region Execute
            internal virtual void Execute()
            {
                if (IsBusy)
                {
                    return;
                }

                try
                {
                    Logger.WriteInformation("Running job {0}", Argument);

                    ThreadStart ts = new ThreadStart
                    (
                        delegate() 
                        {
                            try
                            {
                                InvokeExecuteJob();
                            }
                            catch (ThreadAbortException ex)
                            {
                                // Eat up?

                                Logger.WriteDebug("Job aborted by request");
                                ex.ToString();
                            }
                            catch (Exception ex)
                            {
                                Logger.WriteError("Job execution failed unexpectedly.");
                                Logger.WriteException(ex);
                            }
                            finally
                            {
                                JobThread = null;
                            }
                        }
                    );
                    
                    JobThread = new Thread(ts);
                    JobThread.IsBackground = true;

                    JobThread.Start();

                    Logger.WriteInformation("Job {0} finished", Argument);
                }
                catch (Exception ex)
                {
                    Logger.WriteError("Excpetion while starting job-");
                    Logger.WriteException(ex);
                }                
            }
            #endregion

            #region Terminate
            internal void Terminate()
            {
                if ( IsBusy  )
                {
                    JobThread.Abort();
                }
            }
            #endregion

            #region CalculateReccurence
            internal virtual void CalculateReccurence()
            {
                ExecutionTime += Interval;
            }
            #endregion

            #endregion

            #region Private

            #region InvokeExecuteJob
            private void InvokeExecuteJob()
            {
                if (Scheduler != null)
                {
                    Scheduler.InvokeExecuteJob(this);
                }
            }
            #endregion

            #endregion

            #endregion
        }
        #endregion

        #region Jobs
        class DailyJob : SchedulerJobBase
        {
            #region Construction
            public DailyJob(DateTime executionHour)
                : base(executionHour, TimeSpan.FromDays(1))
            {
            }
            #endregion
        }
        class HourlyJob : SchedulerJobBase
        {
            #region Construction
            public HourlyJob(DateTime executionMinute)
                : base(executionMinute, TimeSpan.FromHours(1))
            {
            }
            #endregion
        }
        class YearlyJob : SchedulerJobBase
        {
            public YearlyJob(DateTime executionTime)
                : base(ScheduleRecurrence.Reccuring)
            {
                ExecutionTime = executionTime;
                Interval = TimeSpan.FromDays(DateTime.IsLeapYear(DateTime.Now.Year) ? 366 : 365);
            }

            internal override void CalculateReccurence()
            {
                ExecutionTime =
                    new DateTime(ExecutionTime.Year + 1, ExecutionTime.Month, ExecutionTime.Day, ExecutionTime.Hour, ExecutionTime.Minute, ExecutionTime.Second);
                Interval = TimeSpan.FromDays(DateTime.IsLeapYear(DateTime.Now.Year) ? 366 : 365);
            }
        }
        class WeeklyJob : SchedulerJobBase
        {
            public WeeklyJob(DayOfWeek day)
                : this(day, DateTime.Now.Date)
            {
            }
            public WeeklyJob(DayOfWeek day, DateTime executionTime)
                : base(ScheduleRecurrence.Reccuring)
            {
                Interval = TimeSpan.FromDays(7);

                // Calculate day coming in current week. Scheduler will do the rest.
                DateTime now = DateTime.Now;
                int dayInMonth = now.Day + (int)((int)day - (int)now.DayOfWeek);

                if (dayInMonth > DateTime.DaysInMonth(now.Year, now.Month))
                {
                    //dayInMonth -= 8;
                    dayInMonth = dayInMonth - DateTime.DaysInMonth(now.Year, now.Month);
                    now = now.AddMonths(1);
                }

                if ( dayInMonth < 1 )
                {
                    dayInMonth = 1;
                }

                ExecutionTime = new DateTime(now.Year, now.Month, dayInMonth, executionTime.Hour, executionTime.Minute, executionTime.Second);
            }
        }
        class MonthlyJob : SchedulerJobBase
        {
            #region Fields
            private DayOfMonth _dayOfMonth;
            private int _originalDay;
            #endregion

            #region Construction
            public MonthlyJob(DayOfMonth day, DateTime executionTime)
                : base(ScheduleRecurrence.Reccuring)
            {
                _dayOfMonth = day;
                _originalDay = executionTime.Day;
                ExecutionTime = executionTime;
                CalculateReccurence();
                Interval = TimeSpan.FromDays(30);
            }
            #endregion

            #region Methods

            #region Internal

            #region CalculateReccurence
            internal override void CalculateReccurence()
            {
                ExecutionTime = ExecutionTime.AddMonths(1);
            }
            #endregion

            #endregion

            #region Private

            #region SetExecutionTime
            private int GetExecutionDay(int year, int month, int day)
            {
                switch (_dayOfMonth)
                {
                    case DayOfMonth.First:
                        return 1;

                    case DayOfMonth.Last:
                        return DateTime.DaysInMonth(year, month);
                    default:
                    case DayOfMonth.Defined:
                        return DateTime.DaysInMonth(year, month) < day ?
                            DateTime.DaysInMonth(year, month) : day;
                }

            }
            #endregion

            #endregion

            #endregion
        }
        #endregion

        #endregion
    } 
    #endregion

    #region ExecuteJobEventArgs
    /// <summary>
    /// Event arguments used when job is executed
    /// </summary>
    public class ExecuteJobEventArgs : EventArgs
    {
        #region Construction
        /// <summary>
        /// COnstruct event arguments with specified <see cref="Scheduler.SchedulerJobBase"/> instance
        /// </summary>
        /// <param name="job"></param>
        public ExecuteJobEventArgs(Scheduler.SchedulerJobBase job)
        {
            _job = job;
        }
        #endregion

        #region Properties

        #region Public

        #region Job
        private Scheduler.SchedulerJobBase _job;
        /// <summary>
        /// Gets the job that spawned event
        /// </summary>
        public Scheduler.SchedulerJobBase Job
        {
            get
            {
                return _job;
            }
        }
        #endregion

        #endregion

        #endregion
    } 
    #endregion

    #region Enumerations
    /// <summary>
    /// Type of schedule reccurence
    /// </summary>
    public enum ScheduleRecurrence : int
    {
        /// <summary>
        /// Job is scheduled once
        /// </summary>
        Once,
        /// <summary>
        /// Job is regulary scheduled with specified time span
        /// </summary>
        Reccuring,
    }

    /// <summary>
    /// Possible days in month for scheduling
    /// </summary>
    public enum DayOfMonth : int
    {
        /// <summary>
        /// First day of month
        /// </summary>
        First = -1,
        /// <summary>
        /// Last day of month
        /// </summary>
        Last = -2,
        /// <summary>
        /// Day is defined
        /// </summary>
        Defined = 0,
    }

    /// <summary>
    /// Possible schedule types
    /// </summary>
    public enum Schedule : int
    {
        /// <summary>
        /// Schedule job once
        /// </summary>
        Once,
        /// <summary>
        /// Schedule job reggulary at specified time span
        /// </summary>
        ByTimeSpan,
        /// <summary>
        /// Schedule job every second
        /// </summary>
        BySecond,
        /// <summary>
        /// Schedule job every minute
        /// </summary>
        ByMinute,
        /// <summary>
        /// Schedule job every hour
        /// </summary>
        ByHour,
        /// <summary>
        /// Schedule job every day
        /// </summary>
        ByDay,
        /// <summary>
        /// Schedule job every week
        /// </summary>
        ByWeek,
        /// <summary>
        /// Schedule job every month
        /// </summary>
        ByMonth,
        /// <summary>
        /// Schedule job once a year
        /// </summary>
        ByYear,
    }
    #endregion
}
